package table

import (
	"lol.com/feb214-game/nest.git/log"
	"lol.com/feb214-game/nest.git/tools/tz"
	"reflect"
	"runtime"
	"sync/atomic"
	"talent.com/cube/common/cache"
	"talent.com/cube/common/cxt"
	"talent.com/cube/common/msg"
	"talent.com/cube/conf/g"
)

func registerTableEvent(tc *cxt.TableContext) {
	tc.Skeleton.RegisterChanRPC(g.EventAddUser, handleAddUser)
	tc.Skeleton.RegisterChanRPC(g.EventRemoveUser, handleExitUser)
	tc.Skeleton.RegisterChanRPC(g.EventQueryTableUsers, handleQueryTableUsers)
	tc.Skeleton.RegisterChanRPC(g.EventUpdateAfterSettle, handleUpdateAfterSettle)

	tc.Skeleton.RegisterChanRPC(g.EventSyncUserST, checkDecorator(handleSyncUserReq))
	tc.Skeleton.RegisterChanRPC(g.EventSyncRoomST, checkDecorator(handleSyncTableReq))
	tc.Skeleton.RegisterChanRPC(g.EventUserBanker, checkDecorator(handleUserBankerSwitch))
	tc.Skeleton.RegisterChanRPC(g.EventUserBet, checkDecorator(handleUserBet))
}

func checkDecorator(f func(*cxt.UserContext, *cxt.TableContext, interface{})) func([]interface{}) {
	return func(args []interface{}) {
		uc := args[0].(*cxt.UserContext)
		tc := args[1].(*cxt.TableContext)
		var req interface{}
		if len(args) > 2 {
			req = args[2] // 收到的消息
		} else {
			req = nil
		}
		if tc == nil || uc == nil {
			log.Error("wrong param for %v from lobby",
				runtime.FuncForPC(reflect.ValueOf(f).Pointer()).Name())
			return
		}
		f(uc, tc, req)
	}
}

func handleAddUser(args []interface{}) {
	uc := args[0].(*cxt.UserContext)
	tc := args[1].(*cxt.TableContext)
	if uc == nil || tc == nil {
		return
	}
	old := tc.Players.GetPlayer(uc.ID)
	//如果没被移除，原来的下注信息还在
	if old != nil {
		//以用户在内存中的信息为准
		uc.BetInfo = old.BetInfo
		uc.Award = old.Award
		//此时总的用户数应该减掉还没移除的1
		atomic.AddInt32(&tc.UserCount, -1)
	}
	tc.Players.SetPlayers(uc.ID, uc)
	uc.TableID = tc.ID
	//如果房间已经满了剔除随机一个机器人
	if tc.Players.Length() > g.TableMaxUserCount && tc.UserCount <= g.TableMaxUserCount {
		tc.Players.Map.Range(func(key, value interface{}) bool {
			Player := value.(*cxt.UserContext)
			if Player.IsRobot() && atomic.LoadInt32(&Player.Disconnect) == 0 {
				atomic.StoreInt32(&Player.Disconnect, 1)
				log.Info("mark robot %v as disconnect for table %v full", Player.ID, tc.ID)
				return false
			}
			return true
		})
	}
}

func removeTableUser(tc *cxt.TableContext, userID uint64) {
	gameLabel := g.GetCodeLabel(tc.GameType)
	uc := tc.Players.GetPlayer(userID)
	tc.Players.DeletePlayers(uc.ID)
	if !uc.IsRobot() {
		//用户可能重新接入其他游戏/房间，此时全局的uc已经被新的覆盖
		if globalUc := cxt.GetUserContext(uc.ID); globalUc == uc {
			g.AllAgents.Delete(userID)
		}
		atomic.AddInt32(&tc.UserCount, -1)
		log.Info("[%v]remove user %v from room %v table %v memory", gameLabel, userID, tc.Kind, tc.ID)
		return
	}
	var sleepSec int
	now := tz.GetNowTs()
	if uc.SleepUntil > now {
		sleepSec = int(uc.SleepUntil - now)
	}
	cache.GetRobotList(tc.GameType, tc.ID).Enqueue(
		uc.UserST, uc.RobotLevel, sleepSec)
	log.Info("[%v]remove level %v robot %v from room %v table %v memory, sleep %vs",
		gameLabel, uc.RobotLevel, userID, tc.Kind, tc.ID, sleepSec)
}

//仅供主协程调用，返回true表示可以移除金币锁
func handleExitUser(args []interface{}) interface{} {
	uc := args[0].(*cxt.UserContext)
	tc := args[1].(*cxt.TableContext)
	if tc.Players.GetPlayer(uc.ID) == nil {
		return true
	}
	removeBanker(tc, uc.ID)
	//当前局的庄家
	if tc.BankerList[0].ID == uc.ID {
		return false
	}
	//如果用户没下注，直接从内存中移除
	if tc.CostCache[uc.ID] == 0 {
		removeTableUser(tc, uc.ID)
		return true
	} else if uc.CostSyncCounter.Cleared() {
		//如果用户没有待同步的数据，可以解除金币锁但要等待结算完毕后再移除内存数据
		atomic.StoreInt32(&uc.Disconnect, 1)
		return true
	}
	return false
}

// 余额落库后内存与数据库进行同步
func handleUpdateAfterSettle(args []interface{}) {
	d := args[0].(*cxt.SyncData)
	tc := cxt.GetTableContext(d.TableID)
	if tc == nil {
		return
	}
	uc := tc.Players.GetPlayer(d.UserId)
	if uc == nil {
		return
	}
	//如果还在这一局，没有并发的场景下，可以更新用户余额
	if d.TermNumber == tc.TermNumber {
		if uc.Credit != d.Award {
			log.Info("update user %v credit from %v to %v from db", d.UserId, uc.Credit, d.Award)
			uc.Credit = d.Award
		}
	}
}

func handleSyncUserReq(uc *cxt.UserContext, tc *cxt.TableContext, _ interface{}) {
	syncUserST(uc, tc)
}

func handleSyncTableReq(uc *cxt.UserContext, tc *cxt.TableContext, _ interface{}) {
	syncRoomST(uc, tc)
}

func handleUserBankerSwitch(uc *cxt.UserContext, tc *cxt.TableContext, req interface{}) {
	info := req.(*msg.BankerReq)
	if info == nil {
		return
	}
	userBanker(uc, tc, info.On)
}
func handleUserBet(uc *cxt.UserContext, tc *cxt.TableContext, req interface{}) {
	bet := req.(*msg.BetReq)
	if bet == nil {
		return
	}
	userBet(uc, tc, bet)
	//g.UserActiveCountChan <- [2]interface{}{uc.GameType, uc.ID}
}

//由于snap是每次生成新的，所以没有并发冲突，无须返回副本
func handleQueryTableUsers(args []interface{}) interface{} {
	tc := args[0].(*cxt.TableContext)
	if tc == nil {
		return nil
	}
	clone := make([]*cxt.TopUserRO, len(tc.TopUserSnap))
	copy(clone, tc.TopUserSnap)
	return clone
}
